import { NGXLogger } from 'ngx-logger';
import { Participant } from "./../entity/TT";
import { Injectable } from "@angular/core";
import {
  TrackWrapper,
  VideoContainer,
  VideoStatus,
  VideoZone,
  TrackType,
  VideoZonePos,
} from "../entity/ORTC";
import { OrtcService } from "./ortc.service";
import {
  EventMessage,
  EventSource,
  EventService,
  ParticipantMuteChangeEvent,
  ParticipantSelectEvent, ParticipantPlayEvent
} from "./event.service";
import { RequestReplyService } from "./request-reply.service";
import { Utils } from "../common/Common";
import { User, UserType } from "../entity/TT";

const TAG = "VideozoneService";

@Injectable({
  providedIn: "root",
})
export class VideozoneService {
  private vzs = {};

  constructor(
    private log: NGXLogger,
    private rtcService: OrtcService,
    private eventService: EventService,
    private requestService: RequestReplyService
  ) {
    this.log.debug(TAG, ".init ...");

    this.eventService.subscribeEvent((evt: EventMessage) => {
      switch (evt.source) {
        case EventSource.USER_JOINED: {
          const data = evt.data;
          this.onUserJoined(data);
          break;
        }

        case EventSource.USER_LEFT: {
          const data = evt.data;
          this.onUserLeft(data);
          break;
        }

        case EventSource.CLOCK_TICK: {
          this.keepalive();
          break;
        }

        case EventSource.SELECT_ALL: {
          const active: boolean = evt.data;
          this.selectAll(active);
          break;
        }

        case EventSource.MUTE: {
          const active: boolean = !evt.data;
          const participants = this.selectedParticipants();
          this.rtcService.sendControllerVoice(participants, active);
          break;
        }

        case EventSource.MUTE_CHANGED: {
          const pmce: ParticipantMuteChangeEvent = evt.data;
          this.setMuteChange(pmce);
          break;
        }

        case EventSource.CONTROLLER_WHITE_BOARD: {
          const active: boolean = evt.data;
          const participants = this.selectedParticipants();
          this.rtcService.sendControllerWhiteBoard(participants, active);
          break;
        }

        case EventSource.GIFT_COIN: {
          const active: number = evt.data;
          this.sendCoin(active);
          break;
        }

        case EventSource.GIFT_FABULOUS: {
          const active: number = evt.data;
          this.sendFabulous(active);
          break;
        }

        case EventSource.GIFT_CHANGED: {
          const participants: string[] = evt.data;
          this.refreshGift(participants);
          break;
        }

        case EventSource.PARTICIPANT_GO_HOME: {
          const participants = this.selectedParticipants();
          this.rtcService.sendControllerStudentGoHome(participants, true);
          break;
        }

        case EventSource.TRACK_CHANGED: {
          this.trackChanged(evt.data);
          break;
        }

        case EventSource.TRACK_PLAYED: {
          this.onTrackPlayed(evt.data);
          break;
        }
      }
    });
  }

  refreshGift(participants: string[]) {
    const vz = this.getVideoZoneByPos(VideoZonePos.VZ_Pos2);

    participants.forEach((participant) => {
      this.requestService
        .GetUser(Utils.GetUinFromParticipant(participant))
        .subscribe((user: User) => {
          if (user) {
            const list = this.listByParticipantId(vz, participant);
            list.forEach((s) => {
              s.user = user;
            });
          }
        });
    });
  }

  setMuteChange(pmce: ParticipantMuteChangeEvent) {
    const vc = this.getByParticipantId(pmce.participantId);
    vc.isMuted = pmce.active;
  }

  sendFabulous(active: number) {
    const participants = this.selectedParticipants();
    const uins = participants.map((p) => Utils.GetUinFromParticipant(p));
    this.requestService.GiveFabulous(uins, active).subscribe((r: boolean) => {
      if (r) {
        this.rtcService.sendControllerGiftFabulous(participants, active);
      }
    });
  }

  sendCoin(active: number) {
    const participants = this.selectedParticipants();
    const uins = participants.map((p) => Utils.GetUinFromParticipant(p));
    this.requestService.GiveCoin(uins, active).subscribe((r: boolean) => {
      if (r) {
        this.rtcService.sendControllerGiftCoin(participants, active);
      }
    });
  }

  keepalive() {
    // this.log.debug(TAG, "keepalive");

    const rusers = this.rtcService.users();
    // this.log.debug(TAG, "keepalive rusers:", rusers);

    const vusers = new Set<string>();
    for (const key in this.vzs) {
      const vz = this.vzs[key];
      this.listAll(vz).forEach((vc: VideoContainer) => {
        // const tw: TrackWrapper = this.rtcService.getRemoteTrack(vc.participantId);
        // this.bind(vz, vc, tw);
        const user = vc.user
        if(user){
          vusers.add(user.uin);
          // const tws = this.rtcService.getRemoteTracksByUin(user.uin);
          // if(tws.length <= 0){
          //   this.log.debug(TAG, 'unset user:', user.uin);
          //   this.unsetUser(vc);
          // }
        }
      });
    }
    // this.log.debug(TAG, 'keepalive vusers:', vusers);

    const adds = new Set<string>();
    rusers.forEach(ru =>{
      if(!vusers.has(ru))
        adds.add(ru);
    })

    const dels = new Set<string>();
    vusers.forEach(vu=>{
      if(!rusers.has(vu)){
        dels.add(vu);
      }
    })

    adds.forEach((uin) => {
      // this.log.debug(TAG, 'keepalive Add User:', uin);
      this.requestService.GetUser(uin).subscribe((_user: User) => {
        this.setUser(_user, null);
      });
    })

    // this.log.debug(TAG, 'keepalive adds:', adds, 'dels', dels);

    dels.forEach(uin => {
      // this.log.debug(TAG, 'keepalive Del User:', uin);
      this.requestService.GetUser(uin).subscribe((_user: User) => {
        this.unsetUser(_user);
      });
    })

    for (const key in this.vzs) {
      const vz = this.vzs[key];
      this.listAll(vz).forEach((vc: VideoContainer) => {
        const user = vc.user;
        // this.log.debug(TAG, 'vc', user.uin, vc);

        const ps = this.rtcService.getUsersByUin(user.uin);
        // this.log.debug(TAG, 'ps', user.uin, ps);
        ps.forEach(p => {
          // this.log.debug(TAG, p.getId(), 'remote tracks', p._tracks );
          if(this.rtcService.getConnectionStatus(p) === 'active'){

            this.rtcService.getUserTracks(p).forEach(t => {
                this.attachTrack(user.uin, p.getId(), vc, t);
            })
          }
        })

      })
    }
    // this.rtcService.clearRemotedTracks();
    // const ts = this.rtcService.getRemoteTracks();
    // // this.log.debug(TAG, 'tws', ts);
    // ts.forEach(tw => {
    //   this.attachTrack(tw);
    // })
  }

  bind(vz: VideoZone, vc: VideoContainer,  tw: TrackWrapper) {
    if(!tw){
      return;
    }
    this.log.debug(TAG, "bind", tw.participant);

    // for (const type in tw.trackMap) {
    // const ot = vc[type + "Track"];
    // const nt = tw.trackMap[type];

    // if (!ot || ot.track.id != nt.track.id) {
    // this.setTrack(vz, vc);
    // }
    // }

    // this.attachTrack(tw)
  }

  onTrackPlayed(evt: ParticipantPlayEvent){
    this.log.debug(TAG, 'onTrackPlayed', evt.participantId)
    const others = this.rtcService.getRemoteTracksByUin(
    Utils.GetUinFromParticipant(evt.participantId));

    this.log.debug(TAG, 'others', evt.participantId, others)

    others.forEach(o => {
        if(o.participant === evt.participantId){
          // o.isPlayed = 0;
          // o.needRemove = false;
          o.isPlayed += 1;
          o.playedTime = Utils.nowTime();
        }else{
          // this.log.debug(TAG, 'needReomve', o.participant)
          // o.needRemove = true;
        }
    });

  }

  onUserJoined(user: any) {
    this.log.debug(TAG, "onUserJoined",  user);
    // const user = this.requestService.GetMeFromParticipant(tw.participant);
    // const uin = '30260855045'
    const uin = Utils.GetUinFromParticipant(user.getId());
    this.requestService.GetUser(uin).subscribe((_user: User) => {
      // if (!user) {
      //   console.warn("user is null");
      //   return;
      // }
      this.setUser(_user, user);
    });
  }

  onUserLeft(user: any) {
    this.log.debug(TAG, "onUserLeft", user.getId());
    // const tw = this.rtcService.getRemoteTrack(tw.participant);
    const uin = Utils.GetUinFromParticipant(user.getId());
    // const vz = this.getVideoZoneByUin(uin);
    // const vc = this.getByUser(uin);
    // if(vc.participantId === tw.participant){

    // }
    // tw.needRemove = true;
    // this.rtcService.removeParticipant(tw.participant);
    this.requestService.GetUser(uin).subscribe((_user: User) => {
    this.unsetUser(_user);
    });
  }

  unsetUser(user: User){
    if(!user){
      this.log.warn('user is null');
      return;
    }
    this.log.debug(TAG, "unsetUser", user.uin);
    let vc: VideoContainer = this.getByUser(user.uin);
    if (vc) {
      this.unsetVC(vc);
    }
  }

  unsetVC(vc: VideoContainer) {
    // this.log.debug(TAG, "unsetUser", uin, tw.participant);
    // const vz : VideoZone= this.getVideoZoneByUin(uin);
    // let vc: VideoContainer = this.getByUser(uin);
    // if (!vc) {
    //     vc = this.getEmpty(vz);
    // }

    if (vc) {
      // const rmIdx = vc.tws.findIndex((e) => e.participant === tw.participant);
      // if(0 <= rmIdx){
      //   this.log.debug(TAG, 'unsetUser remove tw:', tw.participant, rmIdx);
      //   vc.tws.splice(rmIdx,  1 );
      // }

        // const tws = this.rtcService.getRemoteTracksByUin(uin);
        // if (tws.length <= 0) {
          vc.user = null;
          vc.participantId = null;
          vc.status = VideoStatus.NONE;
          vc.videoStatus = VideoStatus.NONE;
          vc.audioStatus = VideoStatus.NONE;
          vc.tw = null;
        }
      // }  else {
      //   vc.status = VideoStatus.ATTACHING;
      //   this.setTrack(vz, vc);
      // }
    // }
  }

  setUser(user: User, ruser: any) {
    this.log.debug(TAG, "setUser", user.uin, ruser);
    // if (!tw.trackMap['audio'] || tw.trackMap['audio'].isEnded() ||
    //     !tw.trackMap['video'] || tw.trackMap['video'].isEnded()) {
    //   this.log.debug('setUser trackMap is empty');
    //   return;
    // }

    const vz = this.getVideoZoneByUser(user);
    if(!vz){
      return;
    }

    let vc: VideoContainer = this.getByUser(user.uin);
    if (!vc) {
        vc = this.getEmpty(vz);
    }
    this.log.debug(TAG, "setUser vc", vc);
    if (vc) {
      vc.user = user;
      vc.status = VideoStatus.ATTACHING;
      if(ruser) this.setTrack(vz, vc, ruser.tracks);
    }
  }

  unsetTrack(vz: VideoZone, vc: VideoContainer, type: TrackType, track: any) {
    const selector: string = type + "#" + type + "_" + vc.ownerId;
    // this.log.debug(TAG, 'unsetTrack', vc, selector, track);
    const v = vz.elementRef.nativeElement.querySelector(selector);
    if (v && track) {
      // this.log.debug(TAG, 'detach', track, v)
      track.detach(v);
    }
  }


  isOk(track: any): boolean {
    return track.stream && track.stream.active;
  }

  isLocal(track: any): boolean {
    return track.isLocal && track.isLocal() ? true : false;
  }

  setTrack(vz: VideoZone, vc: VideoContainer, tracks) {
    this.log.debug(TAG, "setTrack", vc.user.uin, tracks);
    // if(vc.tw){
    //   for (const type in vc.tw.trackMap) {
    //     const track = vc.tw.trackMap[type];
    //     if (this.isOk(track)) {
    //         this.log.debug("setTrack participant:", idx, participant, "isOk=true");
    //         return;
    //     }
    //   }
    // }
    // vc.tws.forEach((tw, idx) => {
      // const participant = tw.participant;
    //   if(!tw.changed){
    //     // this.log.debug(TAG, "setTrack trackMap is not changed", tw);
    //     return;
    // }

      // this.log.debug("setTrack participant:", idx, participant, tw.trackMap);
      // for (const type in tw.trackMap) {
      //   const track = tw.trackMap[type];
      //   if (!track) {
      //     this.log.debug("setTrack track is invalid:", idx, participant, type);
      //     return;
      //   }
        // this.log.debug("setTrack track", type, participant, this.isOk(track),'isP2P', track.isP2P);

        // if (!this.isOk(track)) {
        //   this.log.debug("setTrack participant:", idx, participant, "isOk=false", tw.errors);
        //   tw.errors += 1;

        //   if(tw.errors > 5){
        //     this.log.debug(TAG, 'remove', idx, tw.participant);
        //     vc.tws.splice(idx, 1);
        //   }
        // }

        // const ot = vc[type + "Track"];
        // if (ot) {
        //   // this.log.debug(TAG, "setTrack old track", type, vc.participantId, this.isOk(ot));
        //   if (this.isOk(ot)) {
        //     continue;
        //   }
        // }

        // const selector: string = type + "#" + type + "_" + vc.ownerId;
        // // this.log.debug(TAG, "setTrack participant", participant,"selector", selector);
        // const v = vz.elementRef.nativeElement.querySelector(selector);
        // if (!v) {
        //   console.warn('Can not find videoContainer!', participant );
        //   return;
        // }

        // // if(vc[track.getType() + "Status"] == VideoStatus.ATTACHED){
        // //   return;
        // // }
        // this.log.debug(TAG, 'setTrack attach...', participant, type);

        // track.attach(v);
        // vc.status = VideoStatus.ATTACHED;
        // vc[track.getType() + "Status"] = VideoStatus.ATTACHED;
        // vc[track.getType() + "Track"] = track;
        // vc.participantId = tw.participant;
        // vc.attachedTime = Utils.nowTime();

        // tw.attachedTime = Utils.nowTime();
        // tw.changed = false;

        // this.log.debug(TAG, 'setTrack attached', participant, type);
      // }
    // });
  }

  trackChanged(tw: TrackWrapper) {
    this.log.debug(TAG, 'trackChanged', tw.participant, tw);
    this.attachTrackWrapper(tw);
  }

  attachTrackWrapper(tw: TrackWrapper) {
    // this.log.debug(TAG, 'attachTrack', tw)
    const uin = Utils.GetUinFromParticipant(tw.participant);
    // this.requestService.GetUser(uin).subscribe((user: User) => {
      const vc : VideoContainer = this.getByUser(uin);
      if(!vc){
        return;
      }

      // if(vc.participantId === tw.participant){
      //   return;
      // }

        if(tw.isPlayed >= 2 && tw.participant === vc.participantId){
          return;
        }

      const vz = this.getVideoZoneByUin(uin);
      for (const type in tw.trackMap) {
        const participant = tw.participant;
        if(participant == this.rtcService.myParticipantId() && type == 'audio'){
          this.log.debug('is my audio');
          continue;
        }

         const track = tw.trackMap[type];
         if (!track) {
           this.log.debug("setTrack track is invalid:", participant, type);
           continue;
         }

         const selector: string = type + "#" + type + "_" + vc.ownerId;
         // this.log.debug(TAG, "setTrack participant", participant,"selector", selector);
         const v = vz.elementRef.nativeElement.querySelector(selector);
         if (!v) {
           console.warn('Can not find videoContainer!', participant );
           continue;
         }

         this.log.debug(TAG, 'setTrack attaching', participant, type);
        //  this.log.debug(TAG, 'setTrack connectionStatus', track.getConnectionStatus())
         track.attach(v);
         vc.status = VideoStatus.ATTACHED;
         vc[track.getType() + "Status"] = VideoStatus.ATTACHED;
         vc[track.getType() + "Track"] = track;
         vc.participantId = tw.participant;

          this.log.debug(TAG, 'setTrack attached', participant, type);

          tw.playedTime = Utils.nowTime();
          tw.changed = false;
          tw.needRemove = false;
       }
    // });


  }

  attachTrack(uin:string, participant:string, vc : VideoContainer , track: any) {
    const type = track.getType();
    if(vc.participantId === participant
      &&  vc[type+'Track']
      && vc[type+'Track'].disposed === false
      && vc[type+'Track'].stream.active === true
      && vc[type+'Track'].isP2P == track.isP2P)
      {
        return;
    }

    this.log.debug(TAG, "attachTrack", uin, participant, vc, track);

    const selector: string = type + "#" + type + "_" + vc.ownerId;
    // this.log.debug(TAG, "setTrack participant", participant,"selector", selector);
    const vz = this.getVideoZoneByUin(uin);
    const v = vz.elementRef.nativeElement.querySelector(selector);
         if (!v) {
           console.warn('Can not find videoContainer!', uin );
           return;
         }

         this.log.debug(TAG, 'setTrack attaching', uin, type,track);
        //  this.log.debug(TAG, 'setTrack connectionStatus', track.getConnectionStatus())
         track.attach(v);
         vc.status = VideoStatus.ATTACHED;
         vc[type + "Status"] = VideoStatus.ATTACHED;
         vc[type+ "Track"] = track;
         vc.participantId = participant;

          this.log.debug(TAG, 'setTrack attached', participant, type, track);

          // tw.playedTime = Utils.nowTime();
          // tw.changed = false;
          // tw.needRemove = false;
      //  }
    // });


  }

  listByParticipantId(vz: VideoZone, participantId: string) {
    return this.listAll(vz).filter((vc) => vc.participantId === participantId);
  }

  getByParticipantId(participantId: string) {
    let p = this.listAll(this.getVideoZoneByPos(VideoZonePos.VZ_Pos2)).find(
      (vc) => vc.participantId == participantId
    );
    if (!p) {
      p = this.listAll(this.getVideoZoneByPos(VideoZonePos.VZ_Pos1)).find(
        (vc) => vc.participantId == participantId
      );
    }

    return p;
  }

  getByUser(user: string): VideoContainer {
    if (!user) {
      console.warn("user is invalid");
      return;
    }
    // const vz: VideoZone = this.getVideoZoneByUser(user);
    let vc = this.getByUin(this.getVideoZoneByPos(VideoZonePos.VZ_Pos2), user);
    if(!vc){
      vc = this.getByUin(this.getVideoZoneByPos(VideoZonePos.VZ_Pos1), user);
    }
    return vc;
  }
  getVideoZoneByUin(uin: string): VideoZone {
    const vc = this.getByUin(this.getVideoZoneByPos(VideoZonePos.VZ_Pos2), uin);
    if(vc){
      return this.getVideoZoneByPos(VideoZonePos.VZ_Pos2);
    }
    return  this.getVideoZoneByPos(VideoZonePos.VZ_Pos1);
  }

  getVideoZoneByUser(user: User): VideoZone {
    let vz: VideoZone = null;
    if (user.userType === UserType.Member) {
      vz = this.getVideoZoneByPos(VideoZonePos.VZ_Pos2);
    } else if (user.userType === UserType.Teacher) {
      vz = this.getVideoZoneByPos(VideoZonePos.VZ_Pos1);
    }

    return vz;
  }

  getByUin(vz: VideoZone, uin: string): VideoContainer {
    return this.listAll(vz).find((vc) => vc.user && vc.user.uin == uin);
  }

  getEmpty(vz: VideoZone): VideoContainer {
    return vz.videos.find((vc) => vc.user == null);
  }

  selectedParticipants() {
    const students = this.getAll();
    return students.filter((vc) => vc.active).map((vc) => vc.participantId);
  }

  allParticipants() {
    const students = this.getAll();
    return students.map((vc) => vc.participantId);
  }

  selectAll(active: boolean, send: boolean = true) {
    const students = this.getAll();
    students.forEach((s) => {
      s.active = active;
    });

    if (send) {
      const ids = this.allParticipants();
      this.log.debug("ids", ids);
      setTimeout(() => {
        const pse: ParticipantSelectEvent = {
          participantIds: ids,
          select: active,
        };
        this.eventService.sendEvent(EventSource.PARTICIPANT_SELECT, pse);
      }, 400);
    }
  }

  getAll() {
    const vz = this.getVideoZoneByPos(VideoZonePos.VZ_Pos2);
    const students = this.listAll(vz).filter((vc) => vc.user);

    const vz2 = this.getVideoZoneByPos(VideoZonePos.VZ_Pos1);
    const teachers = this.listAll(vz2).filter((vc) => vc.user);

    return students.concat(teachers);
  }

  listAll(vz: VideoZone): VideoContainer[] {
    const list: VideoContainer[] = [];

    if (vz) {
      const mates = vz.videos;
      for (const mate of mates) {
        if (mate.user) {
          list.push(mate);
        }
      }
    }

    return list;
  }

  getVideoZoneByPos(pos: VideoZonePos): VideoZone {
    return this.vzs[pos];
  }

  register(vz: VideoZone) {
    this.log.debug(TAG, "register", vz);
    this.vzs[vz.position] = vz;

    // const rs: TrackWrapper[] = this.rtcService.getRemoteTracks();
    // rs.forEach((tw) => {
      // this.onUserJoined(tw);
    // });
    this.rtcService.participants.forEach((user) => {
      this.onUserJoined(user);
    });
  }

  unregister(vz: VideoZone) {
    this.log.debug(TAG, "unregister", vz);
    delete this.vzs[vz.position];
  }
}
